/*
 * Extract UBI images.
 *
 * Author: Shibojun
 */
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>

#include "defs.h"
#include "ubi-media.h"
#include "utils.h"
#include "crc32.h"
#include "mtd_swab.h"

#include "unubinize.h"

void free_vi_list(struct volume_info_node *vi_list_head)
{
	struct volume_info_node *vi_node;
	struct leb_list *leb_node;

	if (vi_list_head == NULL)
		return;

	/* free the volume_info_node list */
	vi_node = vi_list_head->next;
	while (vi_node != NULL) {
		struct volume_info_node *next;
		struct leb_list *tmp_node;
		/* free the leb list */
		leb_node = vi_node->leb_list_head;
		if (leb_node != NULL) {
			do {
				tmp_node = leb_node->next;
				free(leb_node);
				leb_node = tmp_node;
			} while (leb_node != vi_node->leb_list_head);
		}
		if (vi_node->name)
			free(vi_node->name);
		/* free the name path */
		if (vi_node->image_path) {
			DeleteFile(vi_node->image_path);
			free(vi_node->image_path);
		}
		next = vi_node->next;
		free(vi_node);
		vi_node = next;
	}
}

/**
 * Read a ubinized img, unpack the iamges in it
 */
int unubinize(char *ubi_image_file, struct volume_info_node *vi_list_head)
{
	/* pebsize and lebsize of the ubi image */
	uint32_t peb_size;
	uint32_t leb_size;
	uint32_t peb_vh_offset;
	uint32_t peb_data_offset;
	uint32_t image_seq;

	/* buffer to store a temp file which will store a volume image */
	char temp_file[BUF_SIZE];
	/* buffer to store misc data */
	char buf[BUF_SIZE];
	/* after got the peb size, it's used to read the data in the peb */
	char *peb_buf = NULL;
	char *data_buf = NULL;
	
	HANDLE h_ubiimg;
	/* to remember the current reading peb offset */
	uint32_t current_peb_offs;

	/* some structs */
	struct ubi_ec_hdr ec_hdr;
	struct ubi_vid_hdr vid_hdr;
	struct ubi_vtbl_record vtbl_rec;
	struct volume_info_node *vi_node;
	struct leb_list *leb_node;

	DWORD read_len;
	DWORD written_len;
	/* current file pointer */
	DWORD current_offs;
	/* the size of the image file */
	uint32_t file_size;
	
	/* return value */
	int ret_val = -1;
	DWORD ret;

	/* ec header values in cpu's endian */
	/* int64_t eh_ec; */
	uint32_t eh_crc;
	uint32_t eh_vh_offset;
	uint32_t eh_data_offset;
	uint32_t eh_image_seq;
	
	/* vid header values in cpu's endian */
	uint32_t vh_vol_id;
	uint32_t vh_lnum;
	uint32_t vh_data_size;
	uint32_t vh_used_ebs;
	uint32_t vh_data_pad;
	uint32_t vh_data_crc;
	uint64_t vh_sqnum;
	uint32_t vh_crc;
	
	/* vtbl index to traverse the vtbl */
	uint32_t idx = 0;
	
	/* vtbl record values in cpu's endian */
	uint32_t vr_reserved_pebs;
	uint32_t vr_alignment;
	uint32_t vr_data_pad;
	uint16_t vr_name_len;
	uint32_t vr_crc;

	/**
	 * we remember the max volume id to 
	 * determine the minimum size of the volume table
	 */
	uint32_t max_vol_id = 0;

	/**
	 * Because the peb_size is not stored in the image,
	 * we need to get it through an other way.
	 * According to the ubinize program,
	 * we know that the volume table is resides in peb1 and peb2,
	 * which are at the beginning of the image.
	 * Because UBI_MAX_VOLUMES = 128,
	 * so we scan the first 128 ubi_vtbl_records in peb1,
	 * and caculate the total pebs the all volumes use.
	 * thus we can get the peb_size by the image size and total pebs.
	 * Note that the layout volume uses another two pebs.
	 * Something should be considered:
	 * 1. the peb_size may less than 128 * sizeof(ubi_vtbl_record) = 22016.
     * 2. the record count may less than 128, which mean that
	 *    we will read some meaningless record which may confuse us.
	 * for 1. we check the image size and crc value to ensure the data is valid.
	 * for 2. because the size of a exist volume can't be zero or less,
	 *        so the pebs which a volume uses can't be zero,
	 *        we can check the value of reserved_pebs.
	 * To ensure the correctness, we will calculate the crc all the time,
	 * and after we got the peb_size,
	 * we also compare the content of peb1 and peb2.
	 */

	/** read peb 1 & peb 2
	 * we can get the volume table,
	 * thus we can get the name of a volume
	 * and peb count that each volume uses
	 */

	/* read the first 64 bytes for ec_header */
	h_ubiimg = CreateFile(ubi_image_file, GENERIC_READ, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (h_ubiimg == INVALID_HANDLE_VALUE) {
		errmsg("Open %s failed!: %s\n", ubi_image_file, last_error(msg_buf, BUF_SIZE));
		return -1;
	}
	
	/**
	 * Note: the values read out are big-endian
	 */
	if (!ReadFile(h_ubiimg, &ec_hdr, UBI_EC_HDR_SIZE, &read_len, NULL)
		|| read_len != UBI_EC_HDR_SIZE)
	{
		errmsg("Read %s ec header failed!: %s\n", ubi_image_file,
			last_error(msg_buf, BUF_SIZE));
		goto err_out;
	}
	
	/* Check the magic & version values */
	if (UBI_EC_HDR_MAGIC != be32_to_cpu(ec_hdr.magic)) {
		errmsg("EC header MAGIC is not correct!\n");
		goto err_out;
	}	
	if (ec_hdr.version > UBI_VERSION) {
		errmsg("can only support images under version %d!\n", UBI_VERSION);
		goto err_out;
	}

	/* check the CRC, and return if CRC is not correct */
	eh_crc = be32_to_cpu(ec_hdr.hdr_crc);
	if (eh_crc != mtd_crc32(UBI_CRC32_INIT, &ec_hdr, UBI_EC_HDR_SIZE_CRC)) {
		errmsg("EC header CRC check failed!\n");
		goto err_out;
	}
	
	/**
	 * get the vid_hdr_offset & data_offset, also the imge_seq
	 * they're stored in ec_hdr
	 */
	eh_vh_offset = be32_to_cpu(ec_hdr.vid_hdr_offset);
	eh_data_offset = be32_to_cpu(ec_hdr.data_offset);
	eh_image_seq = be32_to_cpu(ec_hdr.image_seq);

	/* store them in the global variant */
	peb_vh_offset = eh_vh_offset;
	peb_data_offset = eh_data_offset;
	image_seq = eh_image_seq;

	/**
	 * calculate the min_io_size
	 * guess the subpage_size
	 * vid_header will be the next subpage or nand page
	 * data will be the next nand page of vid_header
	 */
	/* the min_io_size and subpage_size are not needed here */

	/**
	 * read the vid_header
	 * check the crc
	 * get the data_size, data_pad, vol_id, lnum
	 */
	current_offs = SetFilePointer(h_ubiimg, eh_vh_offset, NULL, FILE_BEGIN);
	if (current_offs == INVALID_SET_FILE_POINTER) {
		errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
		goto err_out;
	}
	if (!ReadFile(h_ubiimg, &vid_hdr, UBI_VID_HDR_SIZE, &read_len, NULL)
		|| read_len != UBI_VID_HDR_SIZE)
	{
		errmsg("Read %s vid header failed!: %s\n", ubi_image_file,
			last_error(msg_buf, BUF_SIZE));
		goto err_out;
	}

	/* Check the magic & version values */
	if (UBI_VID_HDR_MAGIC != be32_to_cpu(vid_hdr.magic)) {
		errmsg("VID header MAGIC is not correct!\n");
		goto err_out;
	}
	if (vid_hdr.version > UBI_VERSION) {
		errmsg("can only support images under version %d!\n", UBI_VERSION);
		goto err_out;
	}
	/* Check the header crc */
	vh_crc = be32_to_cpu(vid_hdr.hdr_crc);
	if (vh_crc != mtd_crc32(UBI_CRC32_INIT, &vid_hdr, UBI_VID_HDR_SIZE_CRC)) {
		errmsg("VID header CRC check failed!\n");
		goto err_out;
	}

	vh_vol_id = be32_to_cpu(vid_hdr.vol_id);
	vh_lnum = be32_to_cpu(vid_hdr.lnum);
	vh_data_size = be32_to_cpu(vid_hdr.data_size);
	vh_used_ebs = be32_to_cpu(vid_hdr.used_ebs);
	vh_data_pad = be32_to_cpu(vid_hdr.data_pad);
	vh_data_crc = be32_to_cpu(vid_hdr.data_crc);
	vh_sqnum = be64_to_cpu(vid_hdr.sqnum);
	
	/**
	 * Check the volume information to ensure that it's the layout volume
	 */
	if (vh_vol_id != UBI_LAYOUT_VOLUME_ID) {
		errmsg("The first PEB has a wrong id for volume table!\n");
		goto err_out;
	}
	if (vid_hdr.vol_type != UBI_LAYOUT_VOLUME_TYPE) {
		errmsg("The first PEB has a wrong type for volume table!\n");
		goto err_out;
	}
	if (vh_lnum != 0) {
		errmsg("The first PEB has a wrong logical number for volume table!\n");
		goto err_out;
	}
	if (vid_hdr.compat != UBI_LAYOUT_VOLUME_COMPAT) {
		errmsg("The first PEB has a wrong compat flag for volume table!\n");
		goto err_out;
	}
	/**
	 * if the vid header passed all the checks above,
	 * it should almost be the layout vloume.
	 */
	/* scan the volume table */
	current_offs = SetFilePointer(h_ubiimg, eh_data_offset, NULL, FILE_BEGIN);
	if (current_offs == INVALID_SET_FILE_POINTER) {
		errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
		goto err_out;
	}
	vi_list_head->next = NULL;
	vi_node = vi_list_head;
	for (idx = 0; idx < UBI_MAX_VOLUMES; idx++) {
		if (!ReadFile(h_ubiimg, &vtbl_rec, UBI_VTBL_RECORD_SIZE, &read_len, NULL)
			|| read_len != UBI_VTBL_RECORD_SIZE)
		{
			errmsg("Read %s vid header failed!: %s\n", ubi_image_file,
				last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		vr_reserved_pebs = be32_to_cpu(vtbl_rec.reserved_pebs);
		/**
		 * if the value of reserved_pebs is zero,
		 * the record may be not a valid volume record,
		 * so skip it.
		 */
		if (vr_reserved_pebs == 0 || vr_reserved_pebs == 0xFFFFFFFFU) {
			continue;
		}
		/* check the crc */
		vr_crc = be32_to_cpu(vtbl_rec.crc);
		if (vr_crc != mtd_crc32(UBI_CRC32_INIT, &vtbl_rec, UBI_VTBL_RECORD_SIZE_CRC)) {
			//errmsg("volume table record CRC check failed!\n");
			continue;
		}
		vr_alignment = be32_to_cpu(vtbl_rec.alignment);
		vr_data_pad = be32_to_cpu(vtbl_rec.data_pad);
		vr_name_len = be16_to_cpu(vtbl_rec.name_len);

		/* add this vtbl rec to the volume info list */
		vi_node->next = (struct volume_info_node *)calloc(1, sizeof(struct volume_info_node));
		if (vi_node->next == NULL) {
			errmsg("Failed to allocate memory!\n");
			goto err_out1;
		}
		vi_node = vi_node->next;
		vi_node->leb_count = 0;
		vi_node->leb_list_head = NULL;
		vi_node->next = NULL;
		vi_node->vol_id = idx;
		vi_node->vol_type = vtbl_rec.vol_type;
		if (vi_node->vol_type != UBI_VID_STATIC && vi_node->vol_type != UBI_VID_DYNAMIC) {
			errmsg("unkown volume type: %d\n", vi_node->vol_type);
			goto err_out1;
		}
		vi_node->alignment = vr_alignment;
		vi_node->reserved_pebs = vr_reserved_pebs;
		vi_node->flags = vtbl_rec.flags;
		if (vr_name_len <= 0 || vr_name_len > UBI_VOL_NAME_MAX) {
			errmsg("got a volume without name or name invalid!\n");
			goto err_out1;
		}
		vi_node->name = (char *)malloc(vr_name_len + 1);
		if (vi_node->name == NULL) {
			errmsg("Failed to allocate memory!\n");
			goto err_out1;
		}
		memcpy(vi_node->name, vtbl_rec.name, vr_name_len);
		vi_node->name_len = vr_name_len;
		vi_node->name[vi_node->name_len] = '\0';
		/**
		 * may be need in the future
		if (vr_data_pad != vh_data_pad) {
			warnmsg("Conflict data pad in the volume table record: %d\n", idx);
		}
		*/
		vi_node->data_pad = vr_data_pad;
		/* rememer the id as the current max id */
		max_vol_id = idx;
		/* get a temporary file to store the image */
		ret = GetTempPath(BUF_SIZE, buf);
		if (ret > BUF_SIZE || (ret <= 0)) {
			errmsg("GetTempPath failed with error %s.\n", last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		sprintf(msg_buf, "ubi_vol%d_", idx);
		ret = GetTempFileName(buf, msg_buf, 0, temp_file);
		if (ret == 0) {
			printf ("GetTempFileName failed with error %s.\n", last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		vi_node->image_path = _strdup(temp_file);
	}
	
	/**
	 * now we need to get the peb size before doing anything else
	 * the are two ways to guess the peb size:
	 * 1. read the first peb untill we get a valid ec header,
	 *    that's the start of the second peb.
	 * 2. because the size of a pebs must be the power of 2,
	 *    so we can read from the first power of 2 after vid header to 2^n,
	 *    untill we get a valid ec header, or reach the end of the file.
	 * we choose the second way.
	 */
	/* we start our read after the position of max volume record */
	current_offs = eh_data_offset + max_vol_id * UBI_VTBL_RECORD_SIZE;
	peb_size = MIN_PEB_SIZE;
	/* get next position which is power of 2 */
	while (current_offs > peb_size) {
		peb_size <<= 1;
	}
	for (;peb_size <= MAX_PEB_SIZE; peb_size <<= 1) {
		current_offs = SetFilePointer(h_ubiimg, peb_size, NULL, FILE_BEGIN);
		if (current_offs == INVALID_SET_FILE_POINTER) {
			errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		if (!ReadFile(h_ubiimg, &ec_hdr, UBI_EC_HDR_SIZE, &read_len, NULL)
			|| read_len != UBI_EC_HDR_SIZE)
		{
			errmsg("Read %s ec header failed!: %s\n", ubi_image_file,
				last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		/* Check the magic values */
		if (UBI_EC_HDR_MAGIC != be32_to_cpu(ec_hdr.magic)) {
			continue;
		}
		/**
		 * if the magic is correct, we almost get the next peb,
		 * but we still use crc to check
		 */
		eh_crc = be32_to_cpu(ec_hdr.hdr_crc);
		if (eh_crc != mtd_crc32(UBI_CRC32_INIT, &ec_hdr, UBI_EC_HDR_SIZE_CRC)) {
			continue;
		}

		/* check the vid header offset, data offset, and image_seq */
		eh_vh_offset = be32_to_cpu(ec_hdr.vid_hdr_offset);
		eh_data_offset = be32_to_cpu(ec_hdr.data_offset);
		eh_image_seq = be32_to_cpu(ec_hdr.image_seq);
		if (eh_vh_offset != peb_vh_offset) {
			errmsg("not all the pebs have the same vid header offset\n");
			goto err_out1;
		}
		if (eh_data_offset != peb_data_offset) {
			errmsg("not all the pebs have the same data offset\n");
			goto err_out1;
		}
		if (eh_image_seq != image_seq) {
			errmsg("not all the pebs have the same image_seq\n");
			goto err_out1;
		}

		/* now we are in the second peb */
		break;
	}
	if (peb_size > MAX_PEB_SIZE) {
		errmsg("can't get the peb size!\n");
		goto err_out1;
	}
	
	/* check if the peb size is correct through the file size */
	file_size = GetFileSize(h_ubiimg, NULL);
	if (file_size == INVALID_FILE_SIZE) {
		errmsg("Failed to get the file size: %s\n", last_error(msg_buf, BUF_SIZE));
		goto err_out1;
	}
	if (file_size % peb_size != 0) {
		errmsg("Wrong image size or non-standard image.\n");
		goto err_out1;
	}

	/**
	 * the second peb stores the same content of the first peb,
	 * and we will check that to ensure the peb size is correct.
	 */
	current_peb_offs = peb_size;
	/**
	 * read the vid_header
	 * check the crc
	 * get the data_size, data_pad, vol_id, lnum
	 */
	current_offs = SetFilePointer(h_ubiimg, current_peb_offs + eh_vh_offset, NULL, FILE_BEGIN);
	if (current_offs == INVALID_SET_FILE_POINTER) {
		errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
		goto err_out1;
	}
	if (!ReadFile(h_ubiimg, &vid_hdr, UBI_VID_HDR_SIZE, &read_len, NULL)
		|| read_len != UBI_VID_HDR_SIZE)
	{
		errmsg("Read %s vid header failed!: %s\n", ubi_image_file,
			last_error(msg_buf, BUF_SIZE));
		goto err_out1;
	}

	/* Check the magic & version values */
	if (UBI_VID_HDR_MAGIC != be32_to_cpu(vid_hdr.magic)) {
		errmsg("VID header MAGIC is not correct!\n");
		goto err_out1;
	}
	if (vid_hdr.version > UBI_VERSION) {
		errmsg("can only support images under version %d\n", UBI_VERSION);
		goto err_out1;
	}
	/* Check the header crc */
	vh_crc = be32_to_cpu(vid_hdr.hdr_crc);
	if (vh_crc != mtd_crc32(UBI_CRC32_INIT, &vid_hdr, UBI_VID_HDR_SIZE_CRC)) {
		errmsg("VID header CRC check failed!\n");
		goto err_out1;
	}

	vh_vol_id = be32_to_cpu(vid_hdr.vol_id);
	vh_lnum = be32_to_cpu(vid_hdr.lnum);
	
	/**
	 * Check the volume information to ensure that it's the layout volume
	 */
	if (vh_vol_id != UBI_LAYOUT_VOLUME_ID) {
		errmsg("The second PEB has a wrong id for volume table!\n");
		goto err_out1;
	}
	if (vid_hdr.vol_type != UBI_LAYOUT_VOLUME_TYPE) {
		errmsg("The second PEB has a wrong type for volume table!\n");
		goto err_out1;
	}
	if (vh_lnum != 1) {
		errmsg("The second PEB has a wrong logical number for volume table!\n");
		goto err_out1;
	}
	if (vid_hdr.compat != UBI_LAYOUT_VOLUME_COMPAT) {
		errmsg("The second PEB has a wrong compat flag for volume table!\n");
		goto err_out1;
	}
	/**
	 * if the vid header passed all the checks above,
	 * it should almost be the layout vloume.
	 */
	/* scan the volume table */
	current_offs = SetFilePointer(h_ubiimg, current_peb_offs + eh_data_offset, NULL, FILE_BEGIN);
	if (current_offs == INVALID_SET_FILE_POINTER) {
		errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
		goto err_out1;
	}
	for (idx = 0; idx <= max_vol_id; idx++) {
		if (!ReadFile(h_ubiimg, &vtbl_rec, UBI_VTBL_RECORD_SIZE, &read_len, NULL)
			|| read_len != UBI_VTBL_RECORD_SIZE)
		{
			errmsg("Read %s vid header failed!: %s\n", ubi_image_file,
				last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		vr_reserved_pebs = be32_to_cpu(vtbl_rec.reserved_pebs);
		/**
		 * if the value of reserved_pebs is zero,
		 * the record may be not a valid volume record,
		 * so skip it.
		 */
		if (vr_reserved_pebs == 0 || vr_reserved_pebs == 0xFFFFFFFFU) {
			continue;
		}
		/* check the crc */
		vr_crc = be32_to_cpu(vtbl_rec.crc);
		if (vr_crc != mtd_crc32(UBI_CRC32_INIT, &vtbl_rec, UBI_VTBL_RECORD_SIZE_CRC)) {
			//errmsg("volume table record CRC check failed!\n");
			continue;
		}

		vr_alignment = be32_to_cpu(vtbl_rec.alignment);
		vr_data_pad = be32_to_cpu(vtbl_rec.data_pad);
		vr_name_len = be16_to_cpu(vtbl_rec.name_len);

		/**
		 * traverse the volume info list
		 * the manner of the traverse is the simplest type 
		 */
		vi_node = vi_list_head->next;
		while (vi_node != NULL) {
			if (vi_node->vol_id == idx) {
				if (vtbl_rec.vol_type != vi_node->vol_type) {
					errmsg("the volume: %d in first peb and second peb have different types!\n", idx);
					goto err_out1;
				}
				if (vr_reserved_pebs != vi_node->reserved_pebs) {
					errmsg("the volume: %d in first peb and second peb have different reserved_pebs!\n", idx);
					goto err_out1;
				}
				if (vr_alignment != vi_node->alignment) {
					errmsg("the volume: %d in first peb and second peb have different alignments!\n", idx);
					goto err_out1;
				}
				if (vr_data_pad != vi_node->data_pad) {
					errmsg("the volume: %d in first peb and second peb have different data_pads!\n", idx);
					goto err_out1;
				}
				if (vr_name_len != vi_node->name_len) {
					errmsg("the volume: %d in first peb and second peb have different name_lens!\n", idx);
					goto err_out1;
				}
				if (vtbl_rec.flags != vi_node->flags) {
					errmsg("the volume: %d in first peb and second peb have different flags!\n", idx);
					goto err_out1;
				}
			}
			vi_node = vi_node->next;
		}
	}

	/* now we have checked the second peb, and the peb_size must be correct */

	leb_size = peb_size - peb_data_offset;

	/**
	 * scan all the pebs, and build a list of volumes by lnum
	 * we can't assume that all the lebs of a volume are consequent
	 */
	current_peb_offs += peb_size;
	while (current_peb_offs < file_size) {
		current_offs = SetFilePointer(h_ubiimg, current_peb_offs, NULL, FILE_BEGIN);
		if (current_offs == INVALID_SET_FILE_POINTER) {
			errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		if (!ReadFile(h_ubiimg, &ec_hdr, UBI_EC_HDR_SIZE, &read_len, NULL)
			|| read_len != UBI_EC_HDR_SIZE)
		{
			errmsg("Read %s ec header failed!: %s\n", ubi_image_file,
				last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		/* Check the magic & version values */
		if (UBI_EC_HDR_MAGIC != be32_to_cpu(ec_hdr.magic)) {
			errmsg("EC header MAGIC is not correct!\n");
			goto err_out1;
		}
		if (ec_hdr.version > UBI_VERSION) {
			errmsg("can only support images under version %d!\n", UBI_VERSION);
			goto err_out1;
		}
		/* check the CRC */
		eh_crc = be32_to_cpu(ec_hdr.hdr_crc);
		if (eh_crc != mtd_crc32(UBI_CRC32_INIT, &ec_hdr, UBI_EC_HDR_SIZE_CRC)) {
			errmsg("EC header CRC check failed!\n");
			goto err_out1;
		}
				
		/* check the vid header offset, data offset, and image_seq */
		eh_vh_offset = be32_to_cpu(ec_hdr.vid_hdr_offset);
		eh_data_offset = be32_to_cpu(ec_hdr.data_offset);
		eh_image_seq = be32_to_cpu(ec_hdr.image_seq);
		if (eh_vh_offset != peb_vh_offset) {
			errmsg("not all the pebs have the same vid header offset\n");
			goto err_out1;
		}
		if (eh_data_offset != peb_data_offset) {
			errmsg("not all the pebs have the same data offset\n");
			goto err_out1;
		}
		if (eh_image_seq != image_seq) {
			errmsg("not all the pebs have the same image_seq\n");
			goto err_out1;
		}

		/**
		* read the vid_header
		* check the crc
		* get the data_size, data_pad, vol_id, lnum
		*/
		current_offs = SetFilePointer(h_ubiimg, current_peb_offs + eh_vh_offset, NULL, FILE_BEGIN);
		if (current_offs == INVALID_SET_FILE_POINTER) {
			errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}
		if (!ReadFile(h_ubiimg, &vid_hdr, UBI_VID_HDR_SIZE, &read_len, NULL)
			|| read_len != UBI_VID_HDR_SIZE)
		{
			errmsg("Read %s vid header failed!: %s\n", ubi_image_file,
				last_error(msg_buf, BUF_SIZE));
			goto err_out1;
		}

		/* Check the magic & version values */
		if (UBI_VID_HDR_MAGIC != be32_to_cpu(vid_hdr.magic)) {
			errmsg("VID header MAGIC is not correct!\n");
			goto err_out1;
		}
		if (vid_hdr.version > UBI_VERSION) {
			errmsg("can only support images under version %d!\n", UBI_VERSION);
			goto err_out1;
		}
		/* Check the header crc */
		vh_crc = be32_to_cpu(vid_hdr.hdr_crc);
		if (vh_crc != mtd_crc32(UBI_CRC32_INIT, &vid_hdr, UBI_VID_HDR_SIZE_CRC)) {
			errmsg("VID header CRC check failed!\n");
			goto err_out1;
		}

		vh_vol_id = be32_to_cpu(vid_hdr.vol_id);
		vh_lnum = be32_to_cpu(vid_hdr.lnum);
		vh_data_size = be32_to_cpu(vid_hdr.data_size);
		vh_used_ebs = be32_to_cpu(vid_hdr.used_ebs);
		vh_data_pad = be32_to_cpu(vid_hdr.data_pad);
		vh_data_crc = be32_to_cpu(vid_hdr.data_crc);
		vh_sqnum = be32_to_cpu(vid_hdr.sqnum);

		/* find the corresponding volume info node */
		if (!(vi_node && vi_node->vol_id == vh_vol_id)) {
			vi_node = vi_list_head->next;
			while (vi_node != NULL) {
				if (vi_node->vol_id == vh_vol_id)
					break;
				vi_node = vi_node->next;
			}
		}
		/* if the vol id is not in the list */
		if (vi_node == NULL) {
			errmsg("The volume id in this pebs doesn't exist in the volume table!\n");
			goto err_out1;
		}
		/* add a leb list node and remember the data's offset and length */
		leb_node = (struct leb_list *)calloc(1, sizeof(struct leb_list));
		if (leb_node == NULL) {
			errmsg("Failed to allocate memory!\n");
			goto err_out1;
		}
		leb_node->leb_num = vh_lnum;
		leb_node->data_offset = current_peb_offs + eh_data_offset;
		switch (vid_hdr.vol_type) {
		case UBI_VID_DYNAMIC:
			leb_node->data_size = leb_size - vh_data_pad;
			break;
		case UBI_VID_STATIC:
			leb_node->data_size = vh_data_size;
			break;
		}
		
		/**
		 * insert into the list
		 * because we will always get a bigger lnum,
		 * so it's beterr to insert the node at the tail
		 * but we still need to check if we get a smaller lnum
		 */
		if (vi_node->leb_list_head == NULL) {
			leb_node->last = leb_node;
			leb_node->next = leb_node;
			vi_node->leb_list_head = leb_node;
		} else {
			struct leb_list *tmp_node = vi_node->leb_list_head->last;
			while (tmp_node != vi_node->leb_list_head 
				&& tmp_node->leb_num > leb_node->leb_num)
			{
				tmp_node = tmp_node->last;
			}
			if (tmp_node->leb_num > leb_node->leb_num) {
				leb_node->next = tmp_node;
				leb_node->last = tmp_node->last;
				tmp_node->last->next = leb_node;
				tmp_node->last = leb_node;
				vi_node->leb_list_head = leb_node;
			} else {
				leb_node->last = tmp_node;
				leb_node->next = tmp_node->next;
				tmp_node->next->last = leb_node;
				tmp_node->next = leb_node;
			}
		}
		vi_node->leb_count++;
		/* read next peb */
		current_peb_offs += peb_size;
	}
	
	/** read the volume list
	 * write each volume to a image file
	 */
	peb_buf = (char *)malloc(peb_size);
	if (peb_buf == NULL) {
		errmsg("Failed to allocate memory!\n");
		goto err_out1;
	}
	data_buf = peb_buf + peb_data_offset;
	
	vi_node = vi_list_head->next;
	while (vi_node != NULL) {
		if (vi_node->image_path) {
			HANDLE h_image;
			char *file_out;

			h_image = CreateFile(vi_node->image_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			if (h_image == INVALID_HANDLE_VALUE) {
				errmsg("Can't create file %s: %s\n", vi_node->image_path, last_error(msg_buf, BUF_SIZE));
				goto err_out2;
			}
			leb_node = vi_node->leb_list_head;
			if (leb_node != NULL) {
				do {
					uint32_t len_to_read;
					current_offs = SetFilePointer(h_ubiimg, leb_node->data_offset, NULL, FILE_BEGIN);
					if (current_offs == INVALID_SET_FILE_POINTER) {
						errmsg("SetFilePointer failed: %s\n", last_error(msg_buf, BUF_SIZE));
						CloseHandle(h_image);
						DeleteFile(vi_node->image_path);
						goto err_out2;
					}
					if (vi_node->vol_type == UBI_VID_DYNAMIC) {
						len_to_read = leb_size;
					} else {
						len_to_read = leb_node->data_size;
					}
					if (!ReadFile(h_ubiimg, data_buf, len_to_read, &read_len, NULL)
						|| read_len != len_to_read)
					{
						errmsg("Read %s data failed!: %s\n", ubi_image_file, last_error(msg_buf, BUF_SIZE));
						CloseHandle(h_image);
						DeleteFile(vi_node->image_path);
						goto err_out2;
					}
					/**
					 * TODO: maybe need to seek the out file
					 *       to write the leb to the right place.
					 *       because the leb maybe not consequent;
					 */
					if (!WriteFile(h_image, data_buf, len_to_read, &written_len, NULL)
						|| written_len != len_to_read) {
							errmsg("Write %s failed!: %s\n", ubi_image_file, last_error(msg_buf, BUF_SIZE));
							CloseHandle(h_image);
							DeleteFile(vi_node->image_path);
							goto err_out2;
					}
					leb_node = leb_node->next;
				} while (leb_node != vi_node->leb_list_head);
			}
			CloseHandle(h_image);
			if (verbose) {
				file_out= (char *)malloc(strlen(ubi_image_file) + vi_node->name_len + 10);
				if (file_out == NULL) {
					errmsg("Failed to allocate memory!\n");
					goto err_out2;
				}
				sprintf(file_out, "%s.%s.img", ubi_image_file, vi_node->name);
				CopyFile(vi_node->image_path, file_out, FALSE);
				free(file_out);
			}
		}
		vi_node = vi_node->next;
	}
	
	ret_val = 0;

err_out2:
	free(peb_buf);
err_out1:
	/* because we need the file to analyze, we don't delete it currently */
	if (ret_val != 0) {
		free_vi_list(vi_list_head);
	}
err_out:
	CloseHandle(h_ubiimg);
	return ret_val;
}
